1 /* 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2014 Devisualization (Richard Andrew Cattermole) 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in all 14 * copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 module devisualization.window.window; 25 import devisualization.window.interfaces.window; 26 public import devisualization.window.interfaces.window : WindowConfig, Windowable; 27 public import devisualization.window.interfaces.events : MouseButtons, Keys, KeyModifiers; 28 public import devisualization.window.interfaces.context : WindowContextType, IContext; 29 public import devisualization.window.context; 30 import devisualization.window.interfaces.eventable; 31 import std.conv : to; 32 33 class Window : Windowable { 34 private { 35 HWND hwnd_; 36 HICON previousIcon_; 37 wstring lastTitle; 38 bool hasBeenClosed_; 39 40 WindowConfig config_; 41 IContext context_ = null; 42 } 43 44 this(T...)(T config) { this(WindowConfig(config)); } 45 46 this(WindowConfig config) { 47 config_ = config; 48 title = config.title; 49 50 Window this_ = this; 51 hwnd_ = createWindow(config.x, config.y, config.width, config.height, &windowHandler, &this_); 52 } 53 54 static { 55 void messageLoop() { 56 import core.thread : Thread; 57 import core.time : dur; 58 59 while(true) { 60 while(Window.messageLoopIteration()) {} 61 Thread.sleep(dur!"msecs"(50)); 62 } 63 } 64 65 bool messageLoopIteration(uint minBlocking = 0, uint maxNonBlocking = 1) { 66 MSG msg; 67 uint i; 68 69 while (i < minBlocking) { 70 int ret = GetMessageW(&msg, null, 0, 0); 71 72 TranslateMessage(&msg); 73 DispatchMessageW(&msg); 74 75 i++; 76 } 77 78 i = 0; 79 while (i < maxNonBlocking) { 80 int ret = PeekMessageW(&msg, null, 0, 0, PM_REMOVE); 81 82 if (ret == 0) { 83 return false; 84 } else { 85 TranslateMessage(&msg); 86 DispatchMessageW(&msg); 87 } 88 89 i++; 90 } 91 92 return true; 93 } 94 } 95 96 @property { 97 HWND hwnd() 98 in { 99 assert(!hasBeenClosed_); 100 } body { 101 return hwnd_; 102 } 103 104 void title(string value) 105 in { 106 assert(!hasBeenClosed_); 107 } body { 108 title(to!wstring(value)); 109 } 110 111 void title(dstring value) 112 in { 113 assert(!hasBeenClosed_); 114 } body { 115 title(to!wstring(value)); 116 } 117 118 void title(wstring value) 119 in { 120 assert(!hasBeenClosed_); 121 } body { 122 if (value != ""w && value[$-1] != '\0') 123 value ~= '\0'; 124 else if (value == ""w) 125 value = "\0"w; 126 lastTitle = value; 127 128 SetWindowTextW(hwnd_, cast(ushort*)lastTitle.ptr); 129 } 130 131 void size(uint width, uint height) 132 in { 133 assert(!hasBeenClosed_); 134 } body { 135 // calculates correct width/height size of window 136 RECT rect; 137 138 rect.top = 0; 139 rect.left = 0; 140 rect.right = width; 141 rect.bottom = height; 142 143 auto style = GetWindowLongW(hwnd_, GWL_STYLE); 144 if (style == dwFullscreen) { 145 SetWindowLongW(hwnd_, GWL_STYLE, dwStyle); 146 SetWindowLongW(hwnd_, GWL_EXSTYLE, dwExStyle); 147 } 148 149 AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle); 150 width = rect.right; 151 height = rect.bottom; 152 // calculates correct width/height size of window 153 154 SetWindowPos(hwnd_, null, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); 155 } 156 157 void move(int x, int y) 158 in { 159 assert(!hasBeenClosed_); 160 } body { 161 SetWindowPos(hwnd_, null, x, y, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER); 162 } 163 164 void canResize(bool can = true) 165 in { 166 assert(!hasBeenClosed_); 167 } body { 168 auto style = GetWindowLongW(hwnd_, GWL_STYLE); 169 if (can) 170 style |= WS_SIZEBOX | WS_MAXIMIZEBOX; 171 else 172 style &= ~(WS_SIZEBOX | WS_MAXIMIZEBOX); 173 SetWindowLongW(hwnd_, GWL_STYLE, style); 174 } 175 176 void fullscreen(bool isfs = true) 177 in { 178 assert(!hasBeenClosed_); 179 } body { 180 if (isfs) { 181 SetWindowLongW(hwnd_, GWL_STYLE, dwFullscreen); 182 SetWindowLongW(hwnd_, GWL_EXSTYLE, dwExFullscreen); 183 184 int cx = GetSystemMetrics(SM_CXSCREEN); 185 int cy = GetSystemMetrics(SM_CYSCREEN); 186 SetWindowPos(hwnd_, HWND_TOP, 0, 0, cx, cy, SWP_SHOWWINDOW); 187 } else { 188 canResize = true; 189 } 190 } 191 192 void close() 193 in { 194 assert(!hasBeenClosed_); 195 } body { 196 DestroyWindow(hwnd_); 197 CloseWindow(hwnd_); 198 hasBeenClosed_ = true; 199 } 200 201 IContext context() 202 in { 203 assert(!hasBeenClosed_); 204 } body { 205 return context_; 206 } 207 } 208 209 override { 210 void show() 211 in { 212 assert(!hasBeenClosed_); 213 } body { 214 ShowWindow(hwnd_, SW_SHOW); 215 } 216 217 void hide() 218 in { 219 assert(!hasBeenClosed_); 220 } body { 221 ShowWindow(hwnd_, SW_HIDE); 222 } 223 224 void icon(Image image) 225 in { 226 assert(!hasBeenClosed_); 227 assert(image !is null); 228 } body { 229 ubyte[4][] data; 230 foreach(pixel; image.rgba.allPixels) { 231 data ~= [pixel.b_ubyte, pixel.g_ubyte, pixel.r_ubyte, pixel.a_ubyte]; 232 } 233 234 HBITMAP bitmap = CreateBitmap(cast(uint)image.width, cast(uint)image.height, 1, 32, data.ptr); 235 HBITMAP hbmMask = CreateCompatibleBitmap(GetDC(hwnd_), cast(uint)image.width, cast(uint)image.height); 236 237 ICONINFO ii; 238 ii.fIcon = TRUE; 239 ii.hbmColor = bitmap; 240 ii.hbmMask = hbmMask; 241 242 HICON hIcon = CreateIconIndirect(&ii); 243 DeleteObject(hbmMask); 244 245 if (hIcon) { 246 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_BIG, cast(LPARAM)hIcon); 247 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_SMALL, cast(LPARAM)hIcon); 248 } 249 } 250 251 deprecated("Use Devisualization.Image method instead") 252 void icon(ushort width, ushort height, ubyte[3][] idata, ubyte[3]* transparent = null) 253 in { 254 assert(!hasBeenClosed_); 255 assert(width * height == data.length, "Icon pixels length must be equal to width * height"); 256 } body { 257 ubyte[4][] data; 258 foreach(v; idata) { 259 data ~= [v[2], v[1], v[0], 0]; 260 } 261 262 HBITMAP bitmap = CreateBitmap(width, height, 1, 32, data.ptr); 263 HBITMAP hbmMask; 264 if (transparent is null) 265 hbmMask = CreateCompatibleBitmap(GetDC(hwnd_), width, height); 266 else { 267 COLORREF crTransparent = RGB((*transparent)[0], (*transparent)[1], (*transparent)[2]); 268 269 HDC hdcMem, hdcMem2; 270 BITMAP bm; 271 272 GetObjectA(bitmap, BITMAP.sizeof, &bm); 273 hbmMask = CreateBitmap(width, height, 1, 1, null); 274 275 hdcMem = CreateCompatibleDC(GetDC(hwnd_)); 276 hdcMem2 = CreateCompatibleDC(GetDC(hwnd_)); 277 278 SelectObject(hdcMem, bitmap); 279 SelectObject(hdcMem2, hbmMask); 280 281 SetBkColor(hdcMem, crTransparent); 282 BitBlt(hdcMem2, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY); 283 BitBlt(hdcMem, 0, 0, width, height, hdcMem2, 0, 0, SRCINVERT); 284 285 DeleteDC(hdcMem); 286 DeleteDC(hdcMem2); 287 } 288 289 ICONINFO ii; 290 ii.fIcon = TRUE; 291 ii.hbmColor = bitmap; 292 ii.hbmMask = hbmMask; 293 294 HICON hIcon = CreateIconIndirect(&ii); 295 DeleteObject(hbmMask); 296 297 if (hIcon) { 298 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_BIG, cast(LPARAM)hIcon); 299 SendMessageW(hwnd_, WM_SETICON, cast(WPARAM)ICON_SMALL, cast(LPARAM)hIcon); 300 } 301 } 302 303 bool hasBeenClosed() { return hasBeenClosed_; } 304 } 305 306 mixin Eventing!("onDraw", Windowable); 307 mixin Eventing!("onMove", Windowable, int, int); 308 mixin Eventing!("onResize", Windowable, uint, uint); 309 mixin Eventing!("onClose", Windowable); 310 311 mixin Eventing!("onMouseDown", Windowable, MouseButtons, int, int); 312 mixin Eventing!("onMouseMove", Windowable, int, int); 313 mixin Eventing!("onMouseUp", Windowable, MouseButtons); 314 mixin Eventing!("onKeyDown", Windowable, Keys, KeyModifiers); 315 mixin Eventing!("onKeyUp", Windowable, Keys, KeyModifiers); 316 } 317 318 319 private { 320 import windows; 321 322 enum DWORD dwExStyle = 0; 323 enum DWORD dwStyle = WS_OVERLAPPEDWINDOW; 324 325 enum wstring WindowClassName = "Devisualization.Window window\0"w; 326 327 enum DWORD dwFullscreen = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; 328 enum DWORD dwExFullscreen = WS_EX_APPWINDOW | WS_EX_TOPMOST; 329 330 // wasn't in windows bindings woops 331 332 enum GCL_HICON = -14; 333 enum SRCCOPY = 0xCC0020; 334 enum SRCINVERT = 0x660046; 335 336 COLORREF RGB(ubyte r, ubyte g, ubyte b) { 337 r = r & 0xFF; 338 g = g & 0xFF; 339 b = b & 0xFF; 340 return cast(COLORREF)((b << 16) | (g << 8) | r); 341 } 342 343 344 // more actual code 345 346 HWND createWindow(int x, int y, uint width, uint height, WindowProc wProc, Window* windowPtr) { 347 static HINSTANCE hInstance; 348 349 if (hInstance is HINSTANCE.init) 350 hInstance = GetModuleHandleA(null); 351 352 WNDCLASSW wc; 353 wc.lpfnWndProc = wProc; 354 wc.hInstance = hInstance; 355 wc.lpszClassName = cast(ushort*)WindowClassName.ptr; 356 wc.hCursor = LoadCursorW(null, cast(ushort*)IDC_ARROW); 357 wc.style |= CS_OWNDC | CS_HREDRAW | CS_VREDRAW; 358 if (!RegisterClassW(&wc)) 359 throw new WindowNotCreatable(); 360 361 // calculates correct width/height size of window 362 RECT rect; 363 364 rect.top = 0; 365 rect.left = 0; 366 rect.right = width; 367 rect.bottom = height; 368 369 AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle); 370 width = rect.right; 371 height = rect.bottom; 372 // calculates correct width/height size of window 373 374 HWND hwnd = CreateWindowExW( 375 dwExStyle, 376 cast(ushort*)WindowClassName.ptr, 377 null, 378 dwStyle, 379 x, y, 380 width, height, 381 null, 382 null, 383 hInstance, 384 windowPtr); 385 386 if (hwnd is null) 387 throw new WindowNotCreatable(); 388 389 InvalidateRgn(hwnd, null, true); 390 391 return hwnd; 392 } 393 394 extern(Windows) { 395 alias WindowProc = LRESULT function(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam); 396 397 LRESULT windowHandler(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam) { 398 /* 399 * Handles creational arguments aka the current window 400 */ 401 Window window; 402 switch(uMsg) { 403 case WM_CREATE: 404 CREATESTRUCTW pCreate = *cast(CREATESTRUCTW*)lParam; 405 void* pState = pCreate.lpCreateParams; 406 407 version(X86_64) { 408 SetWindowLongPtrW(hwnd, GWLP_USERDATA, *cast(ulong*)pState); 409 } else { 410 SetWindowLongW(hwnd, GWLP_USERDATA, *cast(uint*)pState); 411 } 412 413 return cast(LRESULT)0; 414 415 default: 416 version(X86_64) { 417 window = cast(Window)cast(ulong*)GetWindowLongPtrW(hwnd, GWLP_USERDATA); 418 } else { 419 window = cast(Window)cast(uint*)GetWindowLongW(hwnd, GWLP_USERDATA); 420 } 421 } 422 423 /* 424 * Normal flow handling 425 */ 426 switch(uMsg) { 427 case WM_DESTROY: 428 return cast(LRESULT)0; 429 430 case WM_PAINT: 431 if (window.context_ is null) { 432 if ((window.config_.contextType | WindowContextType.Opengl3Plus) == WindowContextType.Opengl3Plus || (window.config_.contextType | WindowContextType.OpenglLegacy) == WindowContextType.OpenglLegacy) { 433 window.context_ = new OpenglContext(window, window.config_); 434 } else if (window.config_.contextType == WindowContextType.Direct3D) { 435 version(Have_directx_d) { 436 window.context_ = new Direct3dContext(window, window.config_); 437 } 438 } else if (window.config_.contextType == WindowContextType.Buffer2D) { 439 window.context_ = new Buffer2DContext(window, window.config_); 440 } 441 } 442 window.onDraw(); 443 return cast(LRESULT)0; 444 445 case WM_SIZE: 446 window.onResize(LOWORD(lParam), HIWORD(lParam)); 447 return cast(LRESULT)0; 448 449 case WM_MOVE: 450 window.onMove(LOWORD(lParam), HIWORD(lParam)); 451 return cast(LRESULT)0; 452 453 case WM_LBUTTONDOWN: 454 window.onMouseDown(MouseButtons.Left, LOWORD(lParam), HIWORD(lParam)); 455 return cast(LRESULT)0; 456 case WM_MBUTTONDOWN: 457 window.onMouseDown(MouseButtons.Middle, LOWORD(lParam), HIWORD(lParam)); 458 return cast(LRESULT)0; 459 case WM_RBUTTONDOWN: 460 window.onMouseDown(MouseButtons.Right, LOWORD(lParam), HIWORD(lParam)); 461 return cast(LRESULT)0; 462 463 case WM_MOUSEMOVE: 464 window.onMouseMove(LOWORD(lParam), HIWORD(lParam)); 465 return cast(LRESULT)0; 466 467 case WM_LBUTTONUP: 468 window.onMouseUp(MouseButtons.Left); 469 return cast(LRESULT)0; 470 case WM_MBUTTONUP: 471 window.onMouseUp(MouseButtons.Middle); 472 return cast(LRESULT)0; 473 case WM_RBUTTONUP: 474 window.onMouseUp(MouseButtons.Right); 475 return cast(LRESULT)0; 476 477 case WM_SYSKEYDOWN: 478 case WM_KEYDOWN: 479 window.onKeyDown(convertWinKeys(cast(uint)wParam), convertWinKeyModifiers()); 480 return cast(LRESULT)0; 481 482 case WM_SYSKEYUP: 483 case WM_KEYUP: 484 window.onKeyUp(convertWinKeys(cast(uint)wParam), convertWinKeyModifiers()); 485 return cast(LRESULT)0; 486 case WM_CLOSE: 487 window.onClose(); 488 if (window.countOnClose() == 0) 489 window.close(); 490 return cast(LRESULT)0; 491 492 default: 493 break; 494 } 495 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 496 } 497 } 498 499 Keys convertWinKeys(uint code) { 500 switch (code) 501 { 502 case VK_OEM_1: return Keys.Semicolon; 503 case VK_OEM_2: return Keys.Slash; 504 case VK_OEM_PLUS: return Keys.Equals; 505 case VK_OEM_MINUS: return Keys.Hyphen; 506 case VK_OEM_4: return Keys.LeftBracket; 507 case VK_OEM_6: return Keys.RightBracket; 508 case VK_OEM_COMMA: return Keys.Comma; 509 case VK_OEM_PERIOD: return Keys.Period; 510 case VK_OEM_7: return Keys.Quote; 511 case VK_OEM_5: return Keys.Backslash; 512 case VK_OEM_3: return Keys.Tilde; 513 case VK_ESCAPE: return Keys.Escape; 514 case VK_SPACE: return Keys.Space; 515 case VK_RETURN: return Keys.Enter; 516 case VK_BACK: return Keys.Backspace; 517 case VK_TAB: return Keys.Tab; 518 case VK_PRIOR: return Keys.PageUp; 519 case VK_NEXT: return Keys.PageDown; 520 case VK_END: return Keys.End; 521 case VK_HOME: return Keys.Home; 522 case VK_INSERT: return Keys.Insert; 523 case VK_DELETE: return Keys.Delete; 524 case VK_ADD: return Keys.Add; 525 case VK_SUBTRACT: return Keys.Subtract; 526 case VK_MULTIPLY: return Keys.Multiply; 527 case VK_DIVIDE: return Keys.Divide; 528 case VK_PAUSE: return Keys.Pause; 529 case VK_LEFT: return Keys.Left; 530 case VK_RIGHT: return Keys.Right; 531 case VK_UP: return Keys.Up; 532 case VK_DOWN: return Keys.Down; 533 534 default: 535 if (code >= VK_F1 && code <= VK_F12) 536 return cast(Keys)(Keys.F1 + code - VK_F1); 537 else if (code >= VK_NUMPAD0 && code <= VK_NUMPAD9) 538 return cast(Keys)(Keys.Numpad0 + code - VK_NUMPAD0); 539 else if (code >= 'A' && code <= 'Z') 540 return cast(Keys)(Keys.A + code - 'A'); 541 else if (code >= '0' && code <= '9') 542 return cast(Keys)(Keys.Number0 + code - '0'); 543 } 544 545 return Keys.Unknown; 546 } 547 548 KeyModifiers convertWinKeyModifiers() { 549 KeyModifiers ret; 550 551 if (HIWORD(GetKeyState(VK_MENU)) != 0) 552 ret |= KeyModifiers.Alt; 553 if (HIWORD(GetKeyState(VK_CONTROL)) != 0) 554 ret |= KeyModifiers.Control; 555 if (HIWORD(GetKeyState(VK_SHIFT)) != 0 || LOWORD(GetKeyState(VK_CAPITAL)) != 0) 556 ret |= KeyModifiers.Shift; 557 558 return ret; 559 } 560 }